package com.onpositive.data.serializer.readers;

import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
import java.util.LinkedHashSet;

import com.onpositive.data.AbstractReader;
import com.onpositive.data.FilterTester;
import com.onpositive.data.ICanHaveProperty;
import com.onpositive.data.IWritable;
import com.onpositive.data.serializer.writers.AbstractWriter;
import com.onpositive.data.serializer.writers.RangeWriter;
import com.onpositive.datamap.IScalarRange;
import com.onpositive.datamap.core.IDataProperty;
import com.onpositive.datamap.core.impl.DimensionProperty;
import com.onpositive.datamap.values.impl.DimensionRange;
import com.onpositive.datamap.values.impl.ScalarRange;

public class RangeReader extends AbstractReader<ScalarRange> implements
		ICanHaveProperty, IWritable {

	public RangeReader() {
		super(ScalarRange.class);
	}

	protected NumericValuesCollectionReader lows = new NumericValuesCollectionReader();
	protected NumericValuesCollectionReader highs = new NumericValuesCollectionReader();
	protected StringReader comments = new StringReader();
	private IDataProperty property;
	private boolean dimension;

	@Override
	public ScalarRange getObject(int i) {
		if (!isInitialized()) {
			init();
		}
		if (!highs.hasValue(i))
			return null;
		if (dimension) {
			double uVal = highs.getDouble(i);
			if (lows.hasValue(i)) {
				double lVal = lows.getDouble(i);
				return new DimensionRange(property, comments.getObject(i),
						lVal, uVal);
			}
			return new DimensionRange(property, comments.getObject(i), uVal);
		}
		double uVal = highs.getDouble(i);
		if (lows.hasValue(i)) {
			double lVal = lows.getDouble(i);
			return new ScalarRange(property, comments.getObject(i), lVal, uVal);
		}
		return new ScalarRange(property, comments.getObject(i), uVal);
	}

	public ScalarRange getObjectW(int i) {
		if (!isInitialized()) {
			init();
		}
		if (!highs.hasValue(i))
			return null;
		if (dimension) {
			double uVal = highs.getDouble(i);
			if (lows.hasValue(i)) {
				double lVal = lows.getDouble(i);
				return new DimensionRange(property, null, lVal, uVal);
			}
			return new DimensionRange(property, null, uVal);
		}
		double uVal = highs.getDouble(i);
		if (lows.hasValue(i)) {
			double lVal = lows.getDouble(i);
			return new ScalarRange(property, null, lVal, uVal);
		}
		return new ScalarRange(property, null, uVal);
	}

	@Override
	protected void doInit() {
		try {
			int intSize = collectionStart + myStart + INT_SIZE;
			this.file.seek(intSize);
			byte[] byteArr = new byte[12];
			file.read(byteArr, 0, 12);

			ByteBuffer bBuf = ByteBuffer.wrap(byteArr);
			IntBuffer iBuf = bBuf.asIntBuffer();
			int i1 = iBuf.get(0) + intSize + 12;
			int i2 = iBuf.get(1) + intSize + 12;
			int i3 = iBuf.get(2) + intSize + 12;
			lows.setFile(file);
			highs.setFile(file);
			comments.setFile(file);
			lows.setMyStart(intSize + 12);
			lows.setMyEnd(i1);
			highs.setMyStart(i1);
			highs.setMyEnd(i2);
			comments.setMyStart(i2);
			comments.setMyEnd(i3);

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void filterOut(LinkedHashSet<Object> obj, String text) {

	}

	@Override
	public boolean accept(int op, int i, Object constraint) {
		if (!isInitialized()) {
			init();
		}
		if (constraint instanceof ScalarRange) {
			ScalarRange r0 = (ScalarRange) constraint;

			if (!highs.hasValue(i))
				return (op == EQ || op == CONTAINS || op == ONE_OF)
						&& constraint == null;

			double hVal = highs.getDouble(i);
			double lval = hVal;
			double h1 = r0.getHighValue();
			double l1 = r0.getLowValue();
			if (lows.hasValue(i)) {
				lval = lows.getDouble(i);

			}
			switch (op) {
			// TODO THINK ABOUT WHERE CORRECTNESS SHOULD BE
			case FilterTester.GT:
				return l1 < hVal;
			case FilterTester.LT:
				return h1 > lval;
			case FilterTester.GE:
				return l1 <= hVal;
			case FilterTester.LE:
				return h1 >= lval;
			case FilterTester.EQ:
				return h1 == hVal && l1 == lval;
			case FilterTester.NEQ:
				return h1 != hVal || l1 != lval;
			case FilterTester.CONTAINS:
				return (h1 >= hVal&& l1<=hVal)||(h1 >= lval&& l1<=lval);
			default:
				break;
			}
		}
		if (highs.accept(op, i, constraint)
				|| (lows.hasValue(i) && lows.accept(op, i, constraint))) {
			return true;
		}
		return false;
	}

	@Override
	public void seProperty(IDataProperty p) {
		this.property = p;
		dimension = p instanceof DimensionProperty;
	}

	@Override
	public int compare(int num, int num2) {
		if (!isInitialized()) {
			init();
		}
		if (highs.hasValue(num)) {
			double uVal = highs.getDouble(num);
			if (!highs.hasValue(num2)) {
				return 1;
			}
			double uVal1 = highs.getDouble(num2);
			if (uVal > uVal1) {
				return 1;
			}
			if (uVal < uVal1) {
				return -1;
			}
			if (lows.hasValue(num)) {
				if (lows.hasValue(num2)) {
					double double1 = lows.getDouble(num);
					double double2 = lows.getDouble(num2);
					if (double1 > double2) {
						return 1;
					}
					if (double2 < double1) {
						return -1;
					}
					return 0;
				} else {
					return 1;
				}
			}
		} else {
			if (highs.hasValue(num2)) {
				return -1;
			}
		}
		// ScalarRange object = getObjectW(num);
		// ScalarRange object1 =getObjectW(num2);
		// if (object != null) {
		// return object.compareTo(object1);
		// }
		// if (object1 != null) {
		// return -object1.compareTo(object);
		// }
		return num2 - num;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setValue(int index, Object value) {
		if (!isInitialized()){
			init();
		}
		if (value instanceof Number){
			value=new ScalarRange(property, "", ((Number) value).doubleValue());
		}
		
		IScalarRange<Double> r = (IScalarRange<Double>) value;
		if (r != null) {
			lows.setValue(index, r.getLow());
			highs.setValue(index, r.getHigh());
			comments.setValue(index, r.getComment());
		} else {
			lows.setValue(index, null);
			highs.setValue(index, null);
			comments.setValue(index, null);
		}
	}

	@Override
	public void serialize(DataOutputStream ds) throws IOException {
		RangeWriter wm = new RangeWriter();
		int size = lows.size();
		for (int a = 0; a < size; a++) {
			wm.append(getObject(a));
		}
		wm.serialize(ds);
	}
	
	boolean agInited;

	@Override
	public Object aggregate(int op, BitSet ss) {

		if (op == AGR_MIN) {
			Number aggregate0 = (Number) highs.aggregate(op, ss);
			Number aggregate = (Number) lows.aggregate(op, ss);
			return Math.min(aggregate.doubleValue(),aggregate0.doubleValue());
		}
		if (op == AGR_MAX) {
			Object aggregate = highs.aggregate(op, ss);
			return aggregate;
		}		
		return null;
	}

	@Override
	public AbstractWriter<ScalarRange> getWriter() {
		return new RangeWriter();
	}

	@Override
	public void initEmpty(int dataSize) {
		lows.initEmpty(dataSize);
		highs.initEmpty(dataSize);
		comments.initEmpty(dataSize);
		setInitialized();
	}

	@Override
	public void expand(int newSize) {
		if (!isInitialized()){
			init();
		}
		lows.expand(newSize);
		highs.expand(newSize);
		comments.expand(newSize);
	}

}
